﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Windows.Media;
using System.Windows.Interop;
using System.Windows;
using Microsoft.VisualStudio.OLE.Interop;

namespace WpfCap
{
    public class CapDevice:DependencyObject,IDisposable
    {

        ManualResetEvent stopSignal;
        Thread worker;
        IGraphBuilder graph;
        ISampleGrabber grabber;
        IBaseFilter sourceObject, grabberObject;
        IMediaControl control;
        CapGrabber capGrabber;
        static string deviceMoniker;
        IntPtr map;
        IntPtr section;

        public InteropBitmap BitmapSource
        {
            get { return (InteropBitmap)GetValue(BitmapSourceProperty); }
            private set { SetValue(BitmapSourcePropertyKey, value); }
        }

        private static readonly DependencyPropertyKey BitmapSourcePropertyKey =
            DependencyProperty.RegisterReadOnly("BitmapSource", typeof(InteropBitmap), typeof(CapDevice), new UIPropertyMetadata(default(InteropBitmap)));
        public static readonly DependencyProperty BitmapSourceProperty = BitmapSourcePropertyKey.DependencyProperty;



        public float Framerate
        {
            get { return (float)GetValue(FramerateProperty); }
            set { SetValue(FramerateProperty, value); }
        }
        public static readonly DependencyProperty FramerateProperty =
            DependencyProperty.Register("Framerate", typeof(float), typeof(CapDevice), new UIPropertyMetadata(default(float)));




        public CapDevice()
        {
            if (DeviceMonikes.Count() <= 0)
                throw new NotSupportedException("You should connect DirectDraw source first");
            deviceMoniker = @"@device:sw:{860BB310-5D01-11D0-BD3B-00A0C911CE86}\{44A8B5C7-13B6-4211-BD40-35B629D9E6DF}";
            Start();
        }

        public CapDevice(string moniker)
        {
            deviceMoniker = moniker;

            Start();
        }

        public void Start()
        {
            if (worker == null)
            {
                capGrabber = new CapGrabber();
                capGrabber.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(capGrabber_PropertyChanged);
                capGrabber.NewFrameArrived += new EventHandler(capGrabber_NewFrameArrived);

                stopSignal = new ManualResetEvent(false);
                worker = new Thread(RunWorker);
                worker.Start();
            }
            else
            {
                Stop();
                Start();
            }
        }

        void capGrabber_NewFrameArrived(object sender, EventArgs e)
        {
            if (this.Dispatcher!= null)
            {
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render, (SendOrPostCallback)delegate
                {
                    if (BitmapSource != null)
                    {
                        BitmapSource.Invalidate();
                        UpdateFramerate();
                    }
                }, null);
            }
        }


        void capGrabber_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.DataBind, (SendOrPostCallback)delegate
            {

                PixelFormat f = PixelFormats.Bgr32  ;
                

                //f.Masks.Add(m);

                
                //f.Masks.Add(
                if (capGrabber.Width != default(int) && capGrabber.Height != default(int))
                {
                    

                    uint pcount = (uint)(capGrabber.Width * capGrabber.Height * f.BitsPerPixel / 8);
                    section = CreateFileMapping(new IntPtr(-1), IntPtr.Zero, 0x04, 0, pcount, null);
                    map = MapViewOfFile(section, 0xF001F, 0, 0, pcount);
                    BitmapSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromMemorySection(section, capGrabber.Width, capGrabber.Height, f,
                        capGrabber.Width * f.BitsPerPixel / 8, 0) as InteropBitmap;
                    
                    capGrabber.Map = map;
                    if (OnNewBitmapReady != null)
                        OnNewBitmapReady(this, null);
                }
            }, null);
        }

        void UpdateFramerate()
        {
            frames++;
            if (timer.ElapsedMilliseconds >= 1000)
            {
                Framerate = (float)Math.Round(frames * 1000 / timer.ElapsedMilliseconds);
                timer.Reset();
                timer.Start();
                frames = 0;
            }
           
        }


        System.Diagnostics.Stopwatch timer = System.Diagnostics.Stopwatch.StartNew();
        double frames;

        public void Stop()
        {
            if (IsRunning)
            {
                stopSignal.Set();
                worker.Abort();
                if (worker != null)
                {
                    worker.Join();
                    Release();
                }
            }
        }

        public bool IsRunning
        {
            get
            {
                if (worker != null)
                {
                    if (worker.Join(0) == false)
                        return true;

                    Release();
                }
                return false;
            }
        }

        void Release()
        {
            worker = null;

            stopSignal.Close();
            stopSignal = null;
        }

        public static FilterInfo[] DeviceMonikes
        {
            get
            {
                List<FilterInfo> filters = new List<FilterInfo>();
                System.Runtime.InteropServices.ComTypes.IMoniker[] ms = new System.Runtime.InteropServices.ComTypes.IMoniker[1];
                ICreateDevEnum enumD = Activator.CreateInstance(Type.GetTypeFromCLSID(SystemDeviceEnum)) as ICreateDevEnum;
                System.Runtime.InteropServices.ComTypes.IEnumMoniker moniker;
                Guid g = VideoInputDevice;
                if (enumD.CreateClassEnumerator(ref g, out moniker, 0) == 0)
                {

                    while (true)
                    {
                        int r = moniker.Next(1, ms, IntPtr.Zero);
                        if (r != 0 || ms[0] == null)
                            break;
                        filters.Add(new FilterInfo(ms[0]));
                        Marshal.ReleaseComObject(ms[0]);
                        ms[0] = null;

                    }
                }

                return filters.ToArray();
            }
        }

        void RunWorker()
        {
            try
            {
                
                graph = Activator.CreateInstance(Type.GetTypeFromCLSID(FilterGraph)) as IGraphBuilder;
                
                sourceObject = FilterInfo.CreateFilter(deviceMoniker);
                IPin pin=sourceObject.GetPin(PinDirection.Output, 0);

                IAMStreamConfig g = pin as IAMStreamConfig;
               int iCount = 0, iSize = 0;
                g.GetNumberOfCapabilities(out iCount, out iSize);



               
                    // Use the video capabilities structure.
             AMMediaType pmtConfig=null;
                    for (int iFormat = 0; iFormat < iCount; iFormat++)
                    {
                        VideoStreamConfigCaps scc=new VideoStreamConfigCaps();
                       
                       
                        g.GetStreamCaps(iFormat, out pmtConfig,scc);

                        VideoInfoHeader header = (VideoInfoHeader)Marshal.PtrToStructure(pmtConfig.FormatPtr, typeof(VideoInfoHeader));

                        pmtConfig.SubType = MediaSubTypes.RGB32;
                        if (iFormat == 3)
                            g.SetFormat(pmtConfig);
                        /* Examine the format, and possibly use it. */

                        // Delete the media type when you are done.
                        //DeleteMediaType(pmtConfig);

                    }
                
               // IPropertyBag2 q = sourceObject as IPropertyBag2;

                grabber = Activator.CreateInstance(Type.GetTypeFromCLSID(SampleGrabber)) as ISampleGrabber;
                grabberObject = grabber as IBaseFilter;
                
                graph.AddFilter(sourceObject, "source");
                graph.AddFilter(grabberObject, "grabber");
               
                using (AMMediaType mediaType = new AMMediaType())
                {
                    mediaType.MajorType = MediaTypes.Video;
                    mediaType.SubType = MediaSubTypes.RGB32;
                    
                    IPin d = sourceObject.GetPin(PinDirection.Output, 0);
                    IEnumMediaTypes r;
                    d.EnumMediaTypes(out r);
                    IntPtr ptr=new IntPtr();
                    AMMediaType[] mt=new AMMediaType[2];
                    r.Next(1, mt, ptr);

                  //  mt[0].SubType=MediaSubTypes.RGB32;
                   // r.Next(1, mt, ptr);
                    //sourceObject.
                    grabber.SetMediaType(mt[0]);
                    grabberObject.GetPin(PinDirection.Input, 0).ConnectionMediaType( mt[0]);
//                    decode.GetPin(PinDirection.Input, 0).ConnectionMediaType(mt[0]);
                    
                    if (graph.Connect(sourceObject.GetPin(PinDirection.Output, 0), grabberObject.GetPin(PinDirection.Input, 0)) >= 0)
                    //if (graph.Connect(sourceObject.GetPin(PinDirection.Output, 0), decode.GetPin(PinDirection.Input, 0)) >= 0)
                  
                    {
                     //   int jj= graph.Connect(decode.GetPin(PinDirection.Output, 0), colorspace.GetPin(PinDirection.Input, 0));
                 //       IAMStreamConfig cspaceout=(IAMStreamConfig)colorspace.GetPin(PinDirection.Output, 0) ;
                       // cspaceout.SetFormat(pmtConfig);
                    //    int jk=graph.Connect(colorspace.GetPin(PinDirection.Output, 0), grabberObject.GetPin(PinDirection.Input, 0));

                        if (grabber.GetConnectedMediaType(mediaType) == 0)
                        {
                            VideoInfoHeader header = (VideoInfoHeader)Marshal.PtrToStructure(mediaType.FormatPtr, typeof(VideoInfoHeader));
                         // header.BmiHeader.Height = 1080;
                            
                            capGrabber.Width = header.BmiHeader.Width;
                            capGrabber.Height = header.BmiHeader.Height;
                        }
                    }
                   // int i=graph.Render(sourceObject.GetPin(PinDirection.Output, 0));
                   // int i = graph.Render(colorspace.GetPin(PinDirection.Output, 0));
                    grabber.SetBufferSamples(false);
                    grabber.SetOneShot(false);
                    grabber.SetCallback(capGrabber, 1);

                    IVideoWindow wnd = (IVideoWindow)graph;
                    wnd.put_AutoShow(true);
                    wnd.put_FullScreenMode(true);
                    wnd.put_Visible(true);
                    
                    //wnd = null;

                    control = (IMediaControl)graph;
                    control.Run();

                    while (!stopSignal.WaitOne(0, true))
                    {
                        Thread.Sleep(10);
                    }

                    control.StopWhenReady();
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex);
            }
            finally
            {
                graph = null;
                sourceObject = null;
                grabberObject = null;
                grabber = null;
                capGrabber = null;
                control = null;
                
            }
            
        }


        static readonly Guid FilterGraph = new Guid(0xE436EBB3, 0x524F, 0x11CE, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70);

        static readonly Guid SampleGrabber = new Guid(0xC1F400A0, 0x3F08, 0x11D3, 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37);

        public static readonly Guid SystemDeviceEnum = new Guid(0x62BE5D10, 0x60EB, 0x11D0, 0xBD, 0x3B, 0x00, 0xA0, 0xC9, 0x11, 0xCE, 0x86);

        public static readonly Guid VideoInputDevice = new Guid(0x860BB310, 0x5D01, 0x11D0, 0xBD, 0x3B, 0x00, 0xA0, 0xC9, 0x11, 0xCE, 0x86);

        [ComVisible(false)]
        internal class MediaTypes
        {
            public static readonly Guid Video = new Guid(0x73646976, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);

            public static readonly Guid Interleaved = new Guid(0x73766169, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);

            public static readonly Guid Audio = new Guid(0x73647561, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);

            public static readonly Guid Text = new Guid(0x73747874, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);

            public static readonly Guid Stream = new Guid(0xE436EB83, 0x524F, 0x11CE, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70);
        }

        [ComVisible(false)]
        internal class MediaSubTypes
        {
            public static readonly Guid YUYV = new Guid(0x56595559, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);

            public static readonly Guid IYUV = new Guid(0x56555949, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);

            public static readonly Guid DVSD = new Guid(0x44535644, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);

            public static readonly Guid RGB1 = new Guid(0xE436EB78, 0x524F, 0x11CE, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70);

            public static readonly Guid RGB4 = new Guid(0xE436EB79, 0x524F, 0x11CE, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70);

            public static readonly Guid RGB8 = new Guid(0xE436EB7A, 0x524F, 0x11CE, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70);

            public static readonly Guid RGB565 = new Guid(0xE436EB7B, 0x524F, 0x11CE, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70);

            public static readonly Guid RGB555 = new Guid(0xE436EB7C, 0x524F, 0x11CE, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70);

            public static readonly Guid RGB24 = new Guid(0xE436Eb7D, 0x524F, 0x11CE, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70);

            public static readonly Guid RGB32 = new Guid(0xE436EB7E, 0x524F, 0x11CE, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70);

            public static readonly Guid Avi = new Guid(0xE436EB88, 0x524F, 0x11CE, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70);

            public static readonly Guid Asf = new Guid(0x3DB80F90, 0x9412, 0x11D1, 0xAD, 0xED, 0x00, 0x00, 0xF8, 0x75, 0x4B, 0x99);
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr CreateFileMapping(IntPtr hFile, IntPtr lpFileMappingAttributes, uint flProtect, uint dwMaximumSizeHigh, uint dwMaximumSizeLow, string lpName);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, uint dwNumberOfBytesToMap);

        public event EventHandler OnNewBitmapReady;


        #region IDisposable Members

        public void Dispose()
        {
            Stop();
        }

        #endregion
    }

    [ComImport, System.Security.SuppressUnmanagedCodeSecurity,
       Guid("89c31040-846b-11ce-97d3-00aa0055595a"),
       InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IEnumMediaTypes
    {
        [PreserveSig]
        int Next(
            [In] int cMediaTypes,
            [In, Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(EMTMarshaler), SizeParamIndex = 0)] AMMediaType[] ppMediaTypes,
            [In] IntPtr pcFetched
            );

        [PreserveSig]
        int Skip([In] int cMediaTypes);

        [PreserveSig]
        int Reset();

        [PreserveSig]
        int Clone([Out] out IEnumMediaTypes ppEnum);
    }
    internal class EMTMarshaler : DsMarshaler
    {
        public EMTMarshaler(string cookie)
            : base(cookie)
        {
        }

        // Called just after invoking the COM method.  The IntPtr is the same one that just got returned
        // from MarshalManagedToNative.  The return value is unused.
        override public object MarshalNativeToManaged(IntPtr pNativeData)
        {
            AMMediaType[] emt = m_obj as AMMediaType[];

            for (int x = 0; x < emt.Length; x++)
            {
                // Copy in the value, and advance the pointer
                IntPtr p = Marshal.ReadIntPtr(pNativeData, x * IntPtr.Size);
                if (p != IntPtr.Zero)
                {
                    emt[x] = (AMMediaType)Marshal.PtrToStructure(p, typeof(AMMediaType));
                }
                else
                {
                    emt[x] = null;
                }
            }

            return null;
        }

        // The number of bytes to marshal out
        override public int GetNativeDataSize()
        {
            // Get the array size
            int i = ((Array)m_obj).Length;

            // Multiply that times the size of a pointer
            int j = i * IntPtr.Size;

            return j;
        }

        // This method is called by interop to create the custom marshaler.  The (optional)
        // cookie is the value specified in MarshalCookie="asdf", or "" is none is specified.
        public static ICustomMarshaler GetInstance(string cookie)
        {
            return new EMTMarshaler(cookie);
        }
    }
    abstract internal class DsMarshaler : ICustomMarshaler
    {
        #region Data Members
        // The cookie isn't currently being used.
        protected string m_cookie;

        // The managed object passed in to MarshalManagedToNative, and modified in MarshalNativeToManaged
        protected object m_obj;
        #endregion

        // The constructor.  This is called from GetInstance (below)
        public DsMarshaler(string cookie)
        {
            // If we get a cookie, save it.
            m_cookie = cookie;
        }

        // Called just before invoking the COM method.  The returned IntPtr is what goes on the stack
        // for the COM call.  The input arg is the parameter that was passed to the method.
        virtual public IntPtr MarshalManagedToNative(object managedObj)
        {
            // Save off the passed-in value.  Safe since we just checked the type.
            m_obj = managedObj;

            // Create an appropriately sized buffer, blank it, and send it to the marshaler to
            // make the COM call with.
            int iSize = GetNativeDataSize() + 3;
            IntPtr p = Marshal.AllocCoTaskMem(iSize);

            for (int x = 0; x < iSize / 4; x++)
            {
                Marshal.WriteInt32(p, x * 4, 0);
            }

            return p;
        }

        // Called just after invoking the COM method.  The IntPtr is the same one that just got returned
        // from MarshalManagedToNative.  The return value is unused.
        virtual public object MarshalNativeToManaged(IntPtr pNativeData)
        {
            return m_obj;
        }

        // Release the (now unused) buffer
        virtual public void CleanUpNativeData(IntPtr pNativeData)
        {
            if (pNativeData != IntPtr.Zero)
            {
                Marshal.FreeCoTaskMem(pNativeData);
            }
        }

        // Release the (now unused) managed object
        virtual public void CleanUpManagedData(object managedObj)
        {
            m_obj = null;
        }

        // This routine is (apparently) never called by the marshaler.  However it can be useful.
        abstract public int GetNativeDataSize();

        // GetInstance is called by the marshaler in preparation to doing custom marshaling.  The (optional)
        // cookie is the value specified in MarshalCookie="asdf", or "" is none is specified.

        // It is commented out in this abstract class, but MUST be implemented in derived classes
        //public static ICustomMarshaler GetInstance(string cookie)
    }
    [ComImport, System.Security.SuppressUnmanagedCodeSecurity,
    Guid("C6E13340-30AC-11d0-A18C-00A0C9118956"),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IAMStreamConfig
    {
        [PreserveSig]
        int SetFormat([In, MarshalAs(UnmanagedType.LPStruct)] AMMediaType pmt);

        [PreserveSig]
        int GetFormat([Out] out AMMediaType pmt);

        [PreserveSig]
        int GetNumberOfCapabilities(out int piCount, out int piSize);

        [PreserveSig]
        int GetStreamCaps(
            [In] int iIndex,
            [Out] out AMMediaType ppmt,
            [In] VideoStreamConfigCaps pSCC
            );
    }
     [StructLayout(LayoutKind.Sequential)]
    public class VideoStreamConfigCaps
    {
        public Guid guid;
        public AnalogVideoStandard VideoStandard;
        public Size InputSize;
        public Size MinCroppingSize;
        public Size MaxCroppingSize;
        public int CropGranularityX;
        public int CropGranularityY;
        public int CropAlignX;
        public int CropAlignY;
        public Size MinOutputSize;
        public Size MaxOutputSize;
        public int OutputGranularityX;
        public int OutputGranularityY;
        public int StretchTapsX;
        public int StretchTapsY;
        public int ShrinkTapsX;
        public int ShrinkTapsY;
        public long MinFrameInterval;
        public long MaxFrameInterval;
        public int MinBitsPerSecond;
        public int MaxBitsPerSecond;
    }
     [Flags]
     public enum AnalogVideoStandard
     {
         None = 0x00000000,
         NTSC_M = 0x00000001,
         NTSC_M_J = 0x00000002,
         NTSC_433 = 0x00000004,
         PAL_B = 0x00000010,
         PAL_D = 0x00000020,
         PAL_G = 0x00000040,
         PAL_H = 0x00000080,
         PAL_I = 0x00000100,
         PAL_M = 0x00000200,
         PAL_N = 0x00000400,
         PAL_60 = 0x00000800,
         SECAM_B = 0x00001000,
         SECAM_D = 0x00002000,
         SECAM_G = 0x00004000,
         SECAM_H = 0x00008000,
         SECAM_K = 0x00010000,
         SECAM_K1 = 0x00020000,
         SECAM_L = 0x00040000,
         SECAM_L1 = 0x00080000,
         PAL_N_COMBO = 0x00100000,

         NTSCMask = 0x00000007,
         PALMask = 0x00100FF0,
         SECAMMask = 0x000FF000
     }

}
